package com.liuqianfei.markme;

import com.liuqianfei.markme.core.Token;
import com.liuqianfei.markme.rules.Environment;
import com.liuqianfei.markme.rules.IAction;
import com.liuqianfei.markme.rules.Rule;
import com.liuqianfei.markme.rules.Ruler;
import com.liuqianfei.markme.rules.StateBlock;
import com.liuqianfei.markme.rules.block.BlockquoteAction;
import com.liuqianfei.markme.rules.block.CodeAction;
import com.liuqianfei.markme.rules.block.FenceAction;
import com.liuqianfei.markme.rules.block.HeadingAction;
import com.liuqianfei.markme.rules.block.HrAction;
import com.liuqianfei.markme.rules.block.HtmlBlockAction;
import com.liuqianfei.markme.rules.block.LheadingAction;
import com.liuqianfei.markme.rules.block.ListAction;
import com.liuqianfei.markme.rules.block.ParagraphAction;
import com.liuqianfei.markme.rules.block.ReferenceAction;
import com.liuqianfei.markme.rules.block.TableAction;
import com.liuqianfei.util.StringUtil;

import java.util.Arrays;
import java.util.List;

/**
 * internal
 * <p>Block-level tokenizer.
 * @author liuqianfei
 */
public class ParserBlock implements IParser
{
    /**
     * {@link Ruler} instance. Keep configuration of block rules.
     */
    public Ruler ruler;
    
    public ParserBlock()
    {
        ruler = new Ruler();

        Rule rule = new Rule();

        // table
        rule.alts = Arrays.asList("paragraph", "reference");
        ruler.push("table", new TableAction(), rule);

        // code
        ruler.push("code", new CodeAction(), null);

        // fence
        rule.alts = Arrays.asList("paragraph", "reference", "blockquote", "list");
        ruler.push("fence", new FenceAction(), rule);

        // blockquote
        rule.alts = Arrays.asList("paragraph", "reference", "list");
        ruler.push("blockquote", new BlockquoteAction(), rule);

        // hr
        rule.alts = Arrays.asList("paragraph", "reference", "blockquote", "list");
        ruler.push("hr", new HrAction(), rule);

        // list
        rule.alts = Arrays.asList("paragraph", "reference", "blockquote");
        ruler.push("list", new ListAction(), rule);

        // reference
        ruler.push("reference", new ReferenceAction(), null);

        // heading
        rule.alts = Arrays.asList("paragraph", "reference", "blockquote");
        ruler.push("heading", new HeadingAction(), rule);

        // lheading
        ruler.push("lheading", new LheadingAction(), null);

        // html_block
        rule.alts = Arrays.asList("paragraph", "reference", "blockquote");
        ruler.push("html_block", new HtmlBlockAction(), rule);

        // paragraph
        ruler.push("paragraph", new ParagraphAction(), null);
    }
    
    /**
     * Generate tokens for input range
     */
    public void tokenize(StateBlock stateBlock, int startLine, int endLine)
    {
        boolean ok;
        int i;
        List<IAction> rules = this.ruler.getRules("");
        int len = rules.size(), line = startLine;
        boolean hasEmptyLines = false;
        int maxNesting = stateBlock.getMd().configuration.maxNesting;

        while (line < endLine)
        {
            stateBlock.line = line = stateBlock.skipEmptyLines(line);
            if (line >= endLine) { break; }

            // Termination condition for nested calls.
            // Nested calls currently used for blockquotes & lists
            if (stateBlock.getsCount(line) < stateBlock.blkIndent) { break; }

            // If nesting level exceeded - skip tail to the end. That's not ordinary
            // situation and we should not care about content.
            if (stateBlock.level >= maxNesting)
            {
                stateBlock.line = endLine;
                break;
            }

            // Try all possible rules.
            // On success, rule should:
            //
            // - update `state.line`
            // - update `state.tokens`
            // - return true

            for (i = 0; i < len; i++)
            {
                ok = rules.get(i).parse(stateBlock, false, line, endLine);
                if (ok) { break; }
            }

            // set state.tight iff we had an empty line before current tag
            // i.e. latest empty line should not count
            stateBlock.tight = !hasEmptyLines;

            // paragraph might "eat" one newline after it in nested lists
            if (stateBlock.isEmpty(stateBlock.line - 1))
                hasEmptyLines = true;

            line = stateBlock.line;

            if (line < endLine && stateBlock.isEmpty(line))
            {
                hasEmptyLines = true;
                line++;
                stateBlock.line = line;
            }
        }
    }
    
    /**
     * Process input string and push block tokens into `outTokens`
     */
    public void parse(String src, MarkMeParser md, Environment env, List<Token> outTokens)
    {
        if (StringUtil.isEmpty(src))
            return;

        StateBlock stateBlock = new StateBlock(src, md, env, outTokens);

        this.tokenize(stateBlock, stateBlock.line, stateBlock.lineMax);
    }

    @Override
    public Ruler getRuler()
    {
        return ruler;
    }

    @Override
    public Ruler getRuler2()
    {
        return null;
    }
}
